home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 April / PCWorld_2008-04_cd.bin / v cisle / personasfirefox / personas-latest.xpi / chrome / personas.jar / content / personas.js < prev    next >
Text File  |  2008-01-09  |  18KB  |  502 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is Personas.
  15.  *
  16.  * The Initial Developer of the Original Code is Mozilla.
  17.  * Portions created by the Initial Developer are Copyright (C) 2007
  18.  * the Initial Developer. All Rights Reserved.
  19.  *
  20.  * Contributor(s):
  21.  *   Chris Beard <cbeard@mozilla.org>
  22.  *   Myk Melez <myk@mozilla.org>
  23.  *
  24.  * Alternatively, the contents of this file may be used under the terms of
  25.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  26.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  27.  * in which case the provisions of the GPL or the LGPL are applicable instead
  28.  * of those above. If you wish to allow use of your version of this file only
  29.  * under the terms of either the GPL or the LGPL, and not to allow others to
  30.  * use your version of this file under the terms of the MPL, indicate your
  31.  * decision by deleting the provisions above and replace them with the notice
  32.  * and other provisions required by the GPL or the LGPL. If you do not delete
  33.  * the provisions above, a recipient may use your version of this file under
  34.  * the terms of any one of the MPL, the GPL or the LGPL.
  35.  *
  36.  * ***** END LICENSE BLOCK ***** */
  37.  
  38. const PERSONAS_VERSION = "0.9.1";
  39.  
  40. let PersonaController = {
  41.   _defaultToolbarBackgroundImage: null,
  42.   _defaultStatusbarBackgroundImage: null,
  43.  
  44.  
  45.   //**************************************************************************//
  46.   // Shortcuts
  47.  
  48.   // Preference Service
  49.   get _prefSvc() {
  50.     let prefSvc = Cc["@mozilla.org/preferences-service;1"].
  51.                   getService(Ci.nsIPrefBranch);
  52.     prefSvc.QueryInterface(Ci.nsIPrefBranch2);
  53.     delete this._prefSvc;
  54.     this._prefSvc = prefSvc;
  55.     return this._prefSvc;
  56.   },
  57.  
  58.   // Persona Service
  59.   get _personaSvc() {
  60.     let personaSvc = Cc["@mozilla.org/personas/persona-service;1"].
  61.                      getService(Ci.nsIPersonaService);
  62.     delete this._personaSvc;
  63.     this._personaSvc = personaSvc;
  64.     return this._personaSvc;
  65.   },
  66.  
  67.   get _stringBundle() {
  68.     let stringBundle = document.getElementById("personasStringBundle");
  69.     delete this._stringBundle;
  70.     this._stringBundle = stringBundle;
  71.     return this._stringBundle;
  72.   },
  73.  
  74.   get _menu() {
  75.     let menu = document.getElementById("personas-selector-menu");
  76.     delete this._menu;
  77.     this._menu = menu;
  78.     return this._menu;
  79.   },
  80.  
  81.   /**
  82.    * Get the value of a pref, if any; otherwise, get the default value.
  83.    *
  84.    * @param   prefName
  85.    * @param   defaultValue
  86.    * @returns the value of the pref, if any; otherwise, the default value
  87.    */
  88.   _getPref: function(prefName, defaultValue) {
  89.     let prefSvc = this._prefSvc;
  90.  
  91.     try {
  92.       switch (prefSvc.getPrefType(prefName)) {
  93.         case Ci.nsIPrefBranch.PREF_STRING:
  94.           return prefSvc.getCharPref(prefName);
  95.         case Ci.nsIPrefBranch.PREF_INT:
  96.           return prefSvc.getIntPref(prefName);
  97.         case Ci.nsIPrefBranch.PREF_BOOL:
  98.           return prefSvc.getBoolPref(prefName);
  99.       }
  100.     }
  101.     catch (ex) {}
  102.  
  103.     return defaultValue;
  104.   },
  105.  
  106.   // FIXME: for performance, make this a memoizing getter with a pref listener
  107.   // that updates it as the pref changes.
  108.   get _currentPersona() {
  109.     return this._getPref("extensions.personas.selected", "default");
  110.   },
  111.  
  112.   get _baseURL() {
  113.     return this._getPref("extensions.personas.url");
  114.   },
  115.  
  116.   get _locale() {
  117.     switch (this._getPref("general.useragent.locale", "en-US")) {
  118.       case 'ja':
  119.       case 'ja-JP-mac':
  120.         return "ja";
  121.     }
  122.  
  123.     return "en-US";
  124.   },
  125.  
  126.   //**************************************************************************//
  127.   // Initialization
  128.  
  129.   startUp: function() {
  130.     // Get the persona service to ensure it gets initialized and starts updating
  131.     // the lists of categories and personas on a regular basis.
  132.     Cc["@mozilla.org/personas/persona-service;1"].getService();
  133.  
  134.     // Record the default toolbar and statusbar background images so we can
  135.     // revert to them if the user selects the default persona.
  136.     let toolbar = document.getElementById("main-window");
  137.     this._defaultToolbarBackgroundImage = toolbar.style.backgroundImage;
  138.     let statusbar = document.getElementById("status-bar");
  139.     if (statusbar)
  140.       this._defaultStatusbarBackgroundImage = statusbar.style.backgroundImage;
  141.  
  142.     // Check for a first run or updated extension and display some additional
  143.     // information to users.
  144.     let firstRun = this._getPref("extensions.personas.lastversion"); 
  145.     if (firstRun == "firstrun") {
  146.       let firstRunURL = this._baseURL + this._locale + "/firstrun/?version=" + PERSONAS_VERSION;
  147.       setTimeout(function() { window.openUILinkIn(firstRunURL, "tab") }, 500);
  148.       this._prefSvc.setCharPref("extensions.personas.lastversion", PERSONAS_VERSION);
  149.     }
  150.     else if (firstRun != PERSONAS_VERSION) {
  151.       let updatedURL = this._baseURL + this._locale + "/updated/?version=" + PERSONAS_VERSION;
  152.       setTimeout(function() { window.openUILinkIn(updatedURL, "tab") }, 500);
  153.       this._prefSvc.setCharPref("extensions.personas.lastversion", PERSONAS_VERSION);
  154.     }
  155.  
  156.     // Apply the current persona to the browser theme.
  157.     this._updateTheme();
  158.  
  159.     // Observe changes to the selected persona that happen in other windows
  160.     // or by users twiddling the preferences directly.
  161.     this._prefSvc.addObserver("extensions.personas.", this, false);
  162.   },
  163.  
  164.   shutDown: function() {
  165.     this._prefSvc.removeObserver("extensions.personas.", this);
  166.   },
  167.  
  168.   // nsISupports
  169.   QueryInterface: function(aIID) {
  170.     if (aIID == Ci.nsIObserver || aIID == Ci.nsISupports)
  171.       return this;
  172.  
  173.     throw Cr.NS_ERROR_NO_INTERFACE;
  174.   },
  175.  
  176.   // nsIObserver
  177.   observe: function(subject, topic, data) {
  178.     switch (topic) {
  179.       case "nsPref:changed":
  180.         switch (data) {
  181.           case "extensions.personas.selected":
  182.           case "extensions.personas.manualPath":
  183.           case "extensions.personas.category":
  184.             this._updateTheme();
  185.             break;
  186.         }
  187.         break;
  188.     }
  189.   },
  190.  
  191.   /**
  192.    * Set the current persona to the one with the specified ID.
  193.    *
  194.    * @param personaID the ID of the persona to set as the current one.
  195.    */
  196.   _setPersona: function(personaID, categoryID) {
  197.     // Update the list of recent personas.
  198.     if (personaID != "default" && personaID != this._currentPersona && this._currentPersona != "random") {
  199.       this._prefSvc.setCharPref("extensions.personas.lastselected2",
  200.                                 this._getPref("extensions.personas.lastselected1"));
  201.       this._prefSvc.setCharPref("extensions.personas.lastselected1",
  202.                                 this._getPref("extensions.personas.lastselected0"));
  203.       this._prefSvc.setCharPref("extensions.personas.lastselected0", this._currentPersona);
  204.     }
  205.  
  206.     // Save the new selection to prefs.
  207.     //this._prefSvc.setBoolPref("extensions.personas.selectedIsDark", dark);
  208.     this._prefSvc.setCharPref("extensions.personas.selected", personaID);
  209.     this._prefSvc.setCharPref("extensions.personas.category", categoryID);
  210.   },
  211.  
  212.   _getDarkPropertyByPersona: function(personaID) {
  213.  
  214.     // FIXME: temporary hack to get around slow loading on initialization     
  215.     if (!this._personaSvc.personas)
  216.       return false;
  217.  
  218.     let personas = this._personaSvc.personas.wrappedJSObject;
  219.  
  220.     for each (let persona in personas)
  221.       if (persona.id == personaID)
  222.         return typeof persona.dark != "undefined" && persona.dark == "true";
  223.  
  224.     return false;
  225.   },
  226.  
  227.   _getRandomPersonaByCategory: function(currentPersona, categoryID) {
  228.     let personas = this._personaSvc.personas.wrappedJSObject;
  229.     let subList = new Array();
  230.     let k = 0;
  231.  
  232.     // Build the list of possible personas to select from
  233.     for each (let persona in personas) {
  234.       let needle = categoryID;
  235.       let haystack = persona.menu;
  236.  
  237.       if (haystack.search(needle) == -1)
  238.         continue;
  239.  
  240.       subList[k++] = persona;
  241.     }
  242.  
  243.     // Get a random item, trying up to five times to get one that is different
  244.     // from the currently-selected item in the category (if any).
  245.     // We use Math.floor instead of Math.round to pick a random number because
  246.     // the JS reference says Math.round returns a non-uniform distribution
  247.     // <http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Math:random#Examples>.
  248.     let randomIndex, randomItem;
  249.     for (let i = 0; i < 5; i++) {
  250.       randomIndex = Math.floor(Math.random() * subList.length);
  251.       randomItem = subList[randomIndex];
  252.       if (randomItem.id != currentPersona)
  253.         break;
  254.     }
  255.  
  256.     return randomItem.id; 
  257.   },
  258.  
  259.   // FIXME: update the menu item to display the persona name as its label.
  260.   _updateTheme: function() {
  261.     let personaID = this._getPref("extensions.personas.selected") || "default";
  262.  
  263.     // If a random persona has been selected, pick the next one from the category.
  264.     // First check to ensure that the personas list has been updated by the service,
  265.     // as this sometimes doesn't work on startup if your network connection is slow.
  266.     if (personaID == "random") {
  267.       if (this._personaSvc.personas) {
  268.         let categoryID = this._getPref("extensions.personas.category");
  269.         personaID = this._getRandomPersonaByCategory(personaID, categoryID);
  270.         this._prefSvc.setCharPref("extensions.personas.lastrandom", personaID);
  271.       }
  272.       else
  273.         personaID = this._getPref("extensions.personas.lastrandom");
  274.     }
  275.  
  276.     if (personaID == "default")
  277.       this._applyDefault();
  278.     else
  279.       this._applyPersona(personaID);
  280.   },
  281.  
  282.   _applyDefault: function() {
  283.     let toolbar = document.getElementById("main-window");
  284.     toolbar.removeAttribute("persona");
  285.     toolbar.style.backgroundImage = this._defaultToolbarBackgroundImage;
  286.     toolbar.removeAttribute("_personas-dark-style");
  287.  
  288.     let statusbar = document.getElementById("status-bar");
  289.     if (statusbar) {
  290.       statusbar.removeAttribute("persona");
  291.       statusbar.style.backgroundImage = this._defaultStatusbarBackgroundImage;
  292.     }
  293.   },
  294.  
  295.   _applyPersona: function(personaID) {
  296.     let isDark = this._getDarkPropertyByPersona(personaID);
  297.  
  298.     // Style the primary toolbar box, adding the background image and changing
  299.     // the text color to reflect dark vs. light personas as advertised by the feed.
  300.     let toolbar = document.getElementById("main-window");
  301.     toolbar.setAttribute("persona", personaID);
  302.     toolbar.style.backgroundImage = "url('" + this._getToolbarURL(personaID) + "')";
  303.     toolbar.setAttribute("_personas-dark-style", isDark ? "true" : "");
  304.  
  305.     // Style the statusbar, adding the background image.
  306.     let statusbar = document.getElementById("status-bar");
  307.     if (statusbar) {
  308.       statusbar.setAttribute("persona", personaID);
  309.       statusbar.style.backgroundImage = "url('" + this._getStatusbarURL(personaID) + "')";
  310.     }
  311.   },
  312.  
  313.   _getToolbarURL: function(personaID) {
  314.     switch (personaID) {
  315.       case "default":
  316.         return "chrome://personas/skin/default/tbox-default.jpg";
  317.       case "manual":
  318.         return "file://" + this._prefSvc.getCharPref("extensions.personas.manualPath");
  319.     }
  320.  
  321.     return this._baseURL + "skins/" + personaID + "/tbox-" + personaID + ".jpg";
  322.   },
  323.  
  324.   _getStatusbarURL: function(personaID) {
  325.     if (personaID == "default")
  326.       return "chrome://personas/skin/default/stbar-default.jpg";
  327.  
  328.     return this._baseURL + "skins/" + personaID + "/stbar-" + personaID + ".jpg";
  329.   },
  330.  
  331.   onPersonaPopupShowing: function(event) {
  332.     if (event.target != this._menu)
  333.       return;
  334.  
  335.     let categories = this._personaSvc.categories.wrappedJSObject;
  336.     let personas = this._personaSvc.personas.wrappedJSObject;
  337.  
  338.     this._rebuildMenu(categories, personas);
  339.   },
  340.  
  341.   _getCategoryName: function(categoryID) {
  342.     let categories = this._personaSvc.categories.wrappedJSObject;
  343.  
  344.     for each (let category in categories)
  345.       if (category.id == categoryID)
  346.         return category.label;
  347.  
  348.     return "(unknown)";
  349.   },
  350.  
  351.   _getPersonaName: function(personaID) {
  352.     let personas = this._personaSvc.personas.wrappedJSObject;
  353.     let defaultString = this._stringBundle.getString("Default");
  354.  
  355.     if (personaID == "default")
  356.       return defaultString;
  357.  
  358.     for each (let persona in personas)
  359.       if (persona.id == personaID)
  360.         return persona.label;
  361.  
  362.     return defaultString;
  363.   },
  364.  
  365.   _rebuildMenu: function(categories, personas) {
  366.     let openingSeparator = document.getElementById("personasOpeningSeparator");
  367.     let closingSeparator = document.getElementById("personasClosingSeparator");
  368.  
  369.     // Remove everything between the two separators.
  370.     while (openingSeparator.nextSibling && openingSeparator.nextSibling != closingSeparator)
  371.       this._menu.removeChild(openingSeparator.nextSibling);
  372.  
  373.     //document.getElementById("personas-default").disabled = (this.currentPersona == "default");
  374.  
  375.     let personaStatus = document.getElementById("persona-current");
  376.     if (this._currentPersona == "random") {
  377.        personaStatus.setAttribute("class", "menuitem-iconic");
  378.        personaStatus.setAttribute("image", "chrome://personas/skin/random-feed-16x16.png");
  379.        personaStatus.setAttribute("label", this._stringBundle.getString("useRandomPersona.label") + " " +
  380.                                            this._getCategoryName(this._getPref("extensions.personas.category")) + " : " +
  381.                                            this._getPersonaName(this._getPref("extensions.personas.lastrandom")));
  382.     }
  383.     else {
  384.        personaStatus.removeAttribute("class");
  385.        personaStatus.removeAttribute("image");
  386.        personaStatus.setAttribute("label", this._getPersonaName(this._currentPersona));
  387.     }
  388.  
  389.     document.getElementById("personas-manual-separator").hidden =
  390.     document.getElementById("personas-manual").hidden =
  391.       (this._getPref("extensions.personas.editor") != "manual");
  392.  
  393.     for each (let category in categories) {
  394.       let menu = document.createElement("menu");
  395.       menu.setAttribute("label", category.label);
  396.  
  397.       let popupmenu = document.createElement("menupopup");
  398.       popupmenu.setAttribute("id", category.id);
  399.  
  400.       switch (category.type) {
  401.         case "list":
  402.           for each (let persona in personas) {
  403.             let needle = category.id;
  404.             let haystack = persona.menu;
  405.             if (haystack.search(needle) == -1)
  406.               continue;
  407.  
  408.             let item = this._createPersonaItem(persona, category.id);
  409.             popupmenu.appendChild(item);
  410.           }
  411.  
  412.           // Create an item that picks a random persona from the category.
  413.           popupmenu.appendChild(document.createElement("menuseparator"));
  414.  
  415.           let (item = document.createElement("menuitem")) {
  416.             item.setAttribute("personaid", "random");
  417.             item.setAttribute("categoryid", category.id);
  418.             item.setAttribute("class", "menuitem-iconic");
  419.             item.setAttribute("image", "chrome://personas/skin/random-feed-16x16.png");
  420.             item.setAttribute("label", this._stringBundle.getString("useRandomPersona.label") + " " + category.label);
  421.             item.setAttribute("oncommand", "PersonaController.onSelectPersona(event.target);");
  422.             popupmenu.appendChild(item);
  423.           }
  424.  
  425.           break;
  426.  
  427.         case "recent":
  428.           for (let i = 0; i < 3; i++) {
  429.             let recentID = this._getPref("extensions.personas.lastselected" + i);
  430.             if (!recentID)
  431.               continue;
  432.  
  433.             // Find the persona whose ID matches the one in the preference.
  434.             for each (let persona in personas) {
  435.               if (persona.id == recentID) {
  436.                 let item = this._createPersonaItem(persona, "");
  437.                 popupmenu.appendChild(item);
  438.                 break;
  439.               }
  440.             }
  441.           }
  442.           break;
  443.       }
  444.  
  445.       menu.appendChild(popupmenu);
  446.  
  447.       if (category.parent == "top")
  448.         this._menu.insertBefore(menu, closingSeparator);
  449.       else {
  450.         let categoryMenu = document.getElementById(category.parent);
  451.         categoryMenu.insertBefore(menu, categoryMenu.firstChild);
  452.       }
  453.     }
  454.   },
  455.  
  456.   _createPersonaItem: function(persona, categoryid) {
  457.     let item = document.createElement("menuitem");
  458.  
  459.     // We store the ID of the persona in the "personaid" attribute instead of
  460.     // the "id" attribute because "id" has to be unique, and personas sometimes
  461.     // are associated with multiple menuitems (f.e. one in the Recent menu
  462.     // and another in a category menu).
  463.     item.setAttribute("personaid", persona.id);
  464.     item.setAttribute("label", persona.label);
  465.     item.setAttribute("type", "checkbox");
  466.     item.setAttribute("checked", (persona.id == this._currentPersona));
  467.     item.setAttribute("autocheck", "false");
  468.     item.setAttribute("categoryid", categoryid);
  469.     item.setAttribute("oncommand", "PersonaController.onSelectPersona(event.target);");
  470.  
  471.     return item;
  472.   },
  473.  
  474.   onSelectPersona: function(menuitem) {
  475.     let personaID = menuitem.getAttribute("personaid");
  476.     let categoryID = menuitem.getAttribute("categoryid");
  477.     this._setPersona(personaID, categoryID);
  478.   },
  479.  
  480.   onSelectDefault: function() {
  481.     this._setPersona("default", "");
  482.   },
  483.  
  484.   onSelectManual: function(event) {
  485.     let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
  486.     fp.init(window, "Select a File", Ci.nsIFilePicker.modeOpen);
  487.     let result = fp.show();
  488.     if (result == Ci.nsIFilePicker.returnOK) {
  489.       this._prefSvc.setCharPref("extensions.personas.manualPath", fp.file.path);
  490.       this._setPersona("manual", "");
  491.     }
  492.   },
  493.  
  494.   onSelectAbout: function(event) {
  495.     window.openUILinkIn(this._baseURL + this._locale + "/about/?persona=" + this._currentPersona, "tab");
  496.   }
  497.  
  498. };
  499.  
  500. window.addEventListener("load", function(e) { PersonaController.startUp(e); }, false);
  501. window.addEventListener("unload", function(e) { PersonaController.shutDown(e); }, false);
  502.